﻿using System;
using System.Text;
using System.Web.Mvc;
using System.IO;
using System.Web.Security;
using System.Web.Caching;

namespace Pylor.Web
{
    public class AccessControlAttribute : ActionFilterAttribute
    {
        private const string CacheKey = "AccessControlRules";
        private static readonly object Mutex = new object();

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (!Roles.Enabled)
                return;

            Rules rules;

            lock (Mutex)
            {
                rules = (Rules)context.HttpContext.Cache[CacheKey];
                
                if (rules == null)
                {
                    var rulesFilePath = context.HttpContext.Server.MapPath("~/Access-Rules.config");
                    using (var stream = File.OpenRead(rulesFilePath))
                        rules = Rules.Load(stream);

                    context.HttpContext.Cache.Add(CacheKey, rules, null, Cache.NoAbsoluteExpiration,
                                                  new TimeSpan(0, 0, 10, 0), // 10 min 
                                                  CacheItemPriority.Normal, null); 
                }
            }

            if (!context.HttpContext.User.Identity.IsAuthenticated)
                return;

            // obter os roles do utilizador actual
            var currentUserRoles = Roles.GetRolesForUser();
            
            // calcular o resource deste pedido
            var currentResource = GetRequestResource(context);

            if (rules.HasPermission(currentUserRoles, currentResource))
                return;

            throw new ForbiddenException(context.HttpContext, currentResource);
        }

        protected virtual string GetRequestResource(ActionExecutingContext context)
        {
            var sb = new StringBuilder(70);
            sb.Append(context.Controller.GetType().FullName); // controllerTypeName
            sb.Append('.');
            var action = context.ActionDescriptor;
            sb.Append(action.ActionName);
            sb.Append('(');

            var i = 0;
            foreach (var parameter in action.GetParameters())
            {
                sb.Append(parameter.ParameterName);
                sb.Append(',');
                ++i;
            }

            if (i > 0)
                sb.Length--;

            sb.Append(')');

            return sb.ToString();
        }
    }
}